package com.example.codegen;

import com.example.utils.CJUtils;

import java.util.Arrays;
import java.util.Optional;

public class Method implements AstNode {
    // this className main use for file level namespaces generate by mop,
    // not for the initial classes we get from trace, so just use string rather than type.
    public Optional<String> className;
    public String funcName;
    public Type returnType;
    public Statement[] stmts;
    public Parameter[] parameters;
    // any spawn statement in this scope
    public boolean useSpawn;

    public Method(Optional<String> className, String funcName, Type returnType, Statement[] stmts, Parameter[] parameters, boolean useSpawn) {
        this.className = className;
        this.funcName = funcName;
        this.returnType = returnType;
        this.stmts = stmts;
        this.parameters = parameters;
        this.useSpawn = useSpawn;
    }

    public String toJava() {
        // prepration for spawn, init thread pool
        String pre_spawn = useSpawn ? String.format("\nArrayList<Thread> %s = new ArrayList<>();\n", SpawnStatement.threadsPool) : "";
        // join thread pool
        String post_spawn = useSpawn ? String.format("\n%s.forEach(thread -> {\n" + "try {\n" + "thread.join();\n" + "} catch (InterruptedException e) {\n" + "e.printStackTrace();\n" + "}\n" + "});\n", SpawnStatement.threadsPool) : "";
        return "public static " + returnType.toJava() + " " + className.map(s -> s + ".").orElse("") + funcName + "(" + (funcName.equals("main") ? "String[] args" : "") + String.join(", ", Arrays.stream(parameters).map(Parameter::toJava).toArray(String[]::new)) + ")" + "{" + pre_spawn + "\n" + String.join("\n", Arrays.stream(stmts).map(Statement::toJava).toArray(String[]::new)) + post_spawn + "\n}";
    }

    public String toGo() {
        // prepration for spawn, init waitgroup
        String pre_spawn = useSpawn ? String.format("\nvar %s sync.WaitGroup\n", SpawnStatement.goWaitGroup) : "";
        // join thread pool
        String post_spawn = useSpawn ? String.format("\n%s.Wait()\n", SpawnStatement.goWaitGroup) : "";
        return "func " + funcName + "(" + String.join(", ", Arrays.stream(parameters).map(Parameter::toGo).toArray(String[]::new)) + ")" + returnType.toGo() + "{" + pre_spawn + "\n" + String.join("\n", Arrays.stream(stmts).map(Statement::toGo).toArray(String[]::new)) + post_spawn + "\n}";
    }

    @Override
    public String toJs() {
        return "function " + funcName + "(" + String.join(", ", Arrays.stream(parameters).map(Parameter::toJs).toArray(String[]::new)) + ")" + "{" + "\n" + String.join("\n", Arrays.stream(stmts).map(Statement::toJs).toArray(String[]::new)) + "\n}";
    }

    @Override
    public String toCSharp() {
        // prepration for spawn, init thread pool
        String pre_spawn = useSpawn ? String.format("\nList<Thread> %s = new List<Thread>();\n", SpawnStatement.threadsPool) : "";
        // join thread pool
        String post_spawn = useSpawn ? String.format("\n%s.ForEach(thread => {\n" + "thread.Join();\n" + "});\n", SpawnStatement.threadsPool) : "";
        return "public static " + returnType.toCSharp() + " " + className.map(s -> s + ".").orElse("") + (funcName.equals("main") ? "Main" : funcName) + "(" + String.join(", ", Arrays.stream(parameters).map(Parameter::toCSharp).toArray(String[]::new)) + ")" + "{" + pre_spawn + "\n" + String.join("\n", Arrays.stream(stmts).map(Statement::toCSharp).toArray(String[]::new)) + post_spawn + "\n}";
    }

    public String toCJ(){
        String paramList = "";
        for(Parameter par : parameters){
            paramList += par.toCJ() + ", ";
        }
        if(!paramList.equals(""))
            paramList = paramList.substring(0, paramList.length()-2);
        String stmtList = "";
        String returnStmt = "";
        if(stmts.length > 0){
            for (int i = 0; i < stmts.length-1; i++) {
                stmtList += "\t" + stmts[i].toCJ() + "\n";
            }
            if(stmts[stmts.length-1] instanceof ReturnStatement )
                returnStmt = "\t" + stmts[stmts.length-1].toCJ() + "\n";
            else
                stmtList += "\t" + stmts[stmts.length-1].toCJ() + "\n";
        }

        CJUtils.spawnParamSet.clear();

        String classStr = "";
        if(className.isPresent())
            classStr = className.get()+".";
        String preSpawn = "";
        String postSpawn = "";
        if(useSpawn){
            preSpawn = "\tvar futureList = ArrayList<Future<Unit>>()\n";
            postSpawn = "\tfor (fut in futureList) { fut.get() }\n";
        }
        if(funcName.equals("main"))
            return funcName+ "(" + paramList+"): " + returnType.toCJ() + " {\n" + preSpawn + stmtList + postSpawn + returnStmt + "}\n";
        else
            return (CJUtils.inClass ?"public static func " : "func ")+ classStr + funcName+ "(" + paramList+"): " + returnType.toCJ() + " {\n" + preSpawn + stmtList + postSpawn + returnStmt + "}\n";
    }
}
